<?php

/**
 * This is the model class for table "msgg_item_callback".
 *
 * The followings are the available columns in table 'msgg_item_callback':
 * @property integer $id
 * @property integer $iid
 * @property string $type
 * @property string $url
 * @property integer $retry
 * @property string $params
 * @property integer $alive
 * @property string $result
 * @property integer $status
 * @property integer $ts
 * @property integer $time_create
 * @property integer $time_update
 * @property string $date_create
 * @property string $date_update
 */
class MsggItemCallback extends CActiveRecord
{
    use util_array;


    const STATUS_SUCCESS = 0;
    const STATUS_COMPLETE = 1;
    //space 11-19 to define more failure type in the further
    const STATUS_FAILED = 10;

    const STATUS_RUNNING = 30;

    const STATUS_WAITING = 40;
    const STATUS_RETRYING = 41;




    private $_params = null;
    private $_result = null;


    /**
     * @return string the associated database table name
     */
    public function tableName()
    {
        return 'msgg_item_callback';
    }

    /**
     * @return array validation rules for model attributes.
     */
    public function rules()
    {
        return array(
            array('status', 'required'),
            array('iid, retry, alive, ts, time_create, time_update', 'numerical', 'integerOnly' => true),
            array('type', 'length', 'max' => 16),
            array('url', 'length', 'max' => 1024),
            array('params', 'length', 'max' => 128),
            array('result', 'length', 'max' => 64),
            array('date_create, date_update', 'safe'),
            array('id, iid, type, url, retry, params, alive, result, status, ts, time_create, time_update, date_create, date_update', 'safe', 'on' => 'search'),
            array('url', 'url'),
            array(
                'date_update',
                'default',
                'value' => new CDbExpression('NOW()'),
                'setOnEmpty' => false,
                'on' => 'update'
            ),
            array(
                'date_create, date_update',
                'default',
                'value' => new CDbExpression('NOW()'),
                'setOnEmpty' => false,
                'on' => 'insert'
            ),
            array(
                'time_update',
                'default',
                'value' => time(),
                'setOnEmpty' => false,
                'on' => 'update'
            ),
            array(
                'time_create, time_update',
                'default',
                'value' => time(),
                'setOnEmpty' => false,
                'on' => 'insert'
            ),

        );
    }

    /**
     * @return array relational rules.
     */
    public function relations()
    {
        return array();
    }

    /**
     * @return array customized attribute labels (name=>label)
     */
    public function attributeLabels()
    {
        return array(
            'id' => 'ID',
            'iid' => 'Iid',
            'type' => 'Type',
            'url' => 'Url',
            'retry' => 'Retry',
            'params' => 'Params',
            'alive' => 'Alive',
            'result' => 'Result',
            'status' => 'Status',
            'ts' => 'Ts',
            'time_create' => 'Time Create',
            'time_update' => 'Time Update',
            'date_create' => 'Date Create',
            'date_update' => 'Date Update',
        );
    }

    /**
     * Retrieves a list of models based on the current search/filter conditions.
     *
     * Typical usecase:
     * - Initialize the model fields with values from filter form.
     * - Execute this method to get CActiveDataProvider instance which will filter
     * models according to data in model fields.
     * - Pass data provider to CGridView, CListView or any similar widget.
     *
     * @return CActiveDataProvider the data provider that can return the models
     * based on the search/filter conditions.
     */
    public function search()
    {

        $criteria = new CDbCriteria;

        $criteria->compare('id', $this->id);
        $criteria->compare('iid', $this->iid);
        $criteria->compare('type', $this->type, true);
        $criteria->compare('url', $this->url, true);
        $criteria->compare('retry', $this->retry);
        $criteria->compare('params', $this->params, true);
        $criteria->compare('alive', $this->alive);
        $criteria->compare('result', $this->result, true);
        $criteria->compare('status', $this->status, true);
        $criteria->compare('ts', $this->ts);
        $criteria->compare('time_create', $this->time_create);
        $criteria->compare('time_update', $this->time_update);
        $criteria->compare('date_create', $this->date_create, true);
        $criteria->compare('date_update', $this->date_update, true);

        return new CActiveDataProvider($this, array(
            'criteria' => $criteria,
        ));
    }

    /**
     * Returns the static model of the specified AR class.
     * Please note that you should have this exact method in all your CActiveRecord descendants!
     * @param string $className active record class name.
     * @return MsggItemCallback the static model class
     */
    public static function model($className = __CLASS__)
    {
        return parent::model($className);
    }

    public static function build($type, $params, $attrs = [])
    {
        $callback = new self();
        $callback->type = $type;
        $callback->url = self::util_array_get($attrs, 'url', null);
        $callback->iid = self::util_array_get($attrs, 'iid', 0);
        $callback->retry = self::util_array_get($attrs, 'retry', 0);
        $callback->alive = self::util_array_get($attrs, 'alive', 0);
        $callback->status = self::util_array_get($attrs, 'status', self::STATUS_WAITING);
        $callback->ts = self::util_array_get($attrs, 'ts', null);
        $callback->time_create = self::util_array_get($attrs, 'time_create', time());
        $callback->_params = $params;

        return $callback->validate() ? $callback : null;

    }

    protected function beforeSave()
    {
        $this->params = json_encode($this->getParams());
        $this->result = json_encode($this->getResult());
        return parent::beforeSave(); // TODO: Change the autogenerated stub
    }


    const ERROR_CODE_ATTR_INVALID = '2';
    const ERROR_MSG_ATTR_INVALID = 'attr invalid';
    const ERROR_CODE_PARAM_INVALID = '3';
    const ERROR_MSG_PARAM_INVALID = 'params invalid';
    const ERROR_CODE_URL_INVALID = '4';
    const ERROR_MSG_URL_INVALID = 'url invalid';
    const ERROR_CODE_HTTP_FAIL = '5';


    public function execute($sync = false)
    {
        $result = array('code' => 0, 'msg' => '');
        $url = $this->url;
        $params = $this->getParams();
        $results = $this->getResult();
        if (empty($this->ts)) {
            $this->ts = time();
        }
        if($this->isFailed() || $this->isComplete() || $this->isSuccess())
        {
            $result['code'] = 0;
            $result['msg'] = 'callback is already over';
            return $result;
        }
        $this->status = self::STATUS_RUNNING;
        if (!$sync) {
            $this->save();
        }
        try {
            $urlValidate = new CUrlValidator();
            if (!$urlValidate->validateValue($url)) {
                throw  new Exception(self::ERROR_MSG_URL_INVALID, self::ERROR_CODE_URL_INVALID);
            }
            if (($params)==null) {
                throw  new Exception(self::ERROR_MSG_PARAM_INVALID, self::ERROR_CODE_PARAM_INVALID);
            }
            $try = 2;
            $postdata = $params;
            $headers = array(
            );
            while ($try) {
                $try--;
                $cres = Curl::post($url, json_encode($postdata, JSON_UNESCAPED_UNICODE), array(), $headers);
                if ($cres['err']) {
                    if ($try) {
                        continue;
                    }
                    throw new Exception('CurlErr:' . $cres['err'], self::ERROR_CODE_HTTP_FAIL);
                }
                $httpCode = isset($cres['header']['http_code']) ? $cres['header']['http_code'] : '-';
                if ($httpCode != '200') {
                    if ($try) {
                        continue;
                    }
                    throw new Exception('response http code invalid:' . $httpCode, self::ERROR_CODE_HTTP_FAIL);
                }
                if (!empty($cres['content']) && ($resp = json_decode($cres['content'], true)) && isset($resp['code'])) {
                    $this->status = self::STATUS_SUCCESS;
                } else {
                    $this->status = self::STATUS_COMPLETE;
                }
                if (isset($resp)) {
                    $result['data'] = json_encode($resp);
                }
                break;
            }
        } catch (Exception $e) {
            $this->status = self::STATUS_FAILED;
            $result = array('code' => $e->getCode(), 'msg' => $e->getMessage());
        }
        $results[]=$result;
        if ($this->isFailed() && (empty($this->ts) || empty($this->alive) || (time() < (intval($this->ts) + intval($this->alive)))) && (count($results) <= $this->retry)) {
            $this->status = self::STATUS_RETRYING;
        }
        $this->_result = $results;
        if (!$sync) {
            $this->save();
        }
        return $result;
    }

    public static function callBack(array $params = [], $type = null, $attrs = array())
    {
        $callResult = array('code' => 0, 'msg' => '');
        try {
            $callBack = self::build($type, $params, $attrs);
            if (!$callBack) {
                throw  new Exception(self::ERROR_MSG_ATTR_INVALID, self::ERROR_CODE_ATTR_INVALID);
            }
            $try = 0;
            while (($callBack->isWaiting()) && ($try <= $callBack->retry)) {
                $try++;
                $callBack->execute(true);
            }
            $callBack->save();
        } catch (Exception $e) {
            $callResult = ['code' => $e->getCode(), 'msg' => $e->getMessage()];
            Yii::log($e->getMessage(), CLogger::LEVEL_ERROR, 'itemcallback.callback');
        }
        return $callResult;
    }

    public function isFailed()
    {
        return $this->status == self::STATUS_FAILED;
    }
    public function isWaiting()
    {
        return $this->status == self::STATUS_WAITING || $this->status ==self::STATUS_RETRYING;
    }
    public function isSuccess(){
        return $this->status == self::STATUS_SUCCESS;
    }
    public function isComplete()
    {
        return $this->status == self::STATUS_COMPLETE;
    }

    public function getResult()
    {
        if ($this->_result === null) {
            if (!($this->_result = json_decode($this->_result, true))) {
                $this->_result = array();
            }
        }
        return $this->_result;
    }

    public function getParams()
    {
        if ($this->_params === null) {
            if (!($this->_params = json_decode($this->params, true))) {
                $this->_params = array();
            }
        }
        return $this->_params;
    }

}
